﻿using System;
using System.Text;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Diagnostics;
using System.Threading;
using System.Collections.Generic;

namespace System.Net.Sockets
{
    public static class SocketExtensions
    {
        private const int BytesPerLong = 4; // 32 / 8
        private const int BitsPerByte = 8;

        /// <summary>
        /// Sets the keep-alive interval for the socket.
        /// </summary>
        /// <param name="socket">The socket.</param>
        /// <param name="time">Time between two keep alive "pings".</param>
        /// <param name="interval">Time between two keep alive "pings" when first one fails.</param>
        /// <returns>If the keep alive infos were succefully modified.</returns>
        public static bool SetKeepAlive(this Socket socket, ulong time, ulong interval)
        {
            try
            {
                // Array to hold input values.
                var input = new[]
            	{
            		(time == 0 || interval == 0) ? 0UL : 1UL, // on or off
					time,
					interval
				};

                // Pack input into byte struct.
                byte[] inValue = new byte[3 * BytesPerLong];
                for (int i = 0; i < input.Length; i++)
                {
                    inValue[i * BytesPerLong + 3] = (byte)(input[i] >> ((BytesPerLong - 1) * BitsPerByte) & 0xff);
                    inValue[i * BytesPerLong + 2] = (byte)(input[i] >> ((BytesPerLong - 2) * BitsPerByte) & 0xff);
                    inValue[i * BytesPerLong + 1] = (byte)(input[i] >> ((BytesPerLong - 3) * BitsPerByte) & 0xff);
                    inValue[i * BytesPerLong + 0] = (byte)(input[i] >> ((BytesPerLong - 4) * BitsPerByte) & 0xff);
                }

                // Create bytestruct for result (bytes pending on server socket).
                byte[] outValue = BitConverter.GetBytes(0);

                // Write SIO_VALS to Socket IOControl.
                //socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.KeepAlive, true);
                socket.IOControl(IOControlCode.KeepAliveValues, inValue, outValue);
            }
            catch (SocketException e)
            {
                Console.WriteLine("Failed to set keep-alive: {0} {1}", e.ErrorCode, e);
                return false;
            }

            return true;
        }
    }
}


namespace WC
{
    namespace Net
    {
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        //  public class LinkManager
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        public class LinkManager
        {
            public void Add(Socket s)
            {
                lock (_links)
                {
                    Add(new Link(s));
                }
            }
            public void Add(Link lnk)
            {
                lock (_links)
                {
                    _links.Add(lnk.InstanceID, lnk);
                    if (log.IsDebugEnabled) log.DebugFormat("Added a Link({0}), _links.count = {1}", lnk.InstanceID, _links.Count);
                }
            }
            public void Remove(Link lnk)
            {
                lock (_links)
                {
                    _links.Remove(lnk.InstanceID);
                    if (log.IsDebugEnabled) log.DebugFormat("Removed a Link({0}), _links.count = {1}", lnk.InstanceID, _links.Count);
                }
            }
            public int Count { get { return _links.Count; } }
            public bool Contains(Link lnk)
            {
                bool result = false;

                lock (_links)
                {
                    result = _links.ContainsKey(lnk.InstanceID);
                }

                return result;
            }
            //>> 잠시 테스트용....
            public void SendAll(byte[] buffer, int size)
            {
                if (0 == _links.Count)
                    return;

                System.Collections.SortedList links;

                lock (_links)
                {
                    links = (System.Collections.SortedList)_links.Clone();
                }

                long instanceID = 0;
                Link link = null;

                foreach (System.Collections.DictionaryEntry entry in links)
                {
                    instanceID = (long) entry.Key;
                    link = (Link) entry.Value;
                    link.Send(buffer, size, null);
                }
                log.WarnFormat("SendAll: links.Count = {0}", links.Count);
            }

            protected static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
            private System.Collections.SortedList _links = new System.Collections.SortedList();
            //private System.Collections.SortedList _links = System.Collections.SortedList.Synchronized(new System.Collections.SortedList());
        }

        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        //  public class Acceptor
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        public class Acceptor
        {
            public Acceptor()
            {
                //>>
                //>>log4net.Config.XmlConfigurator.Configure(new System.IO.FileInfo("WcLibrary.log4net.xml"));
            }
            //-------------------------------------------------------------------------------------------------------------------
            public bool Start(int port)
            {
                IPEndPoint ep = new IPEndPoint(IPAddress.Any, port);

                try
                {
                    _sock.Bind(ep);
                    _sock.Listen(300);

                    IAsyncResult result = _sock.BeginAccept(new AsyncCallback(Internal_OnAccepted), null);

                    OnAccepted += WC.Pattern.Singleton<WC.Net.NetStatistics>.Instance.OnAccepted;
                }
                catch (SocketException e)
                {
                    log.ErrorFormat("SocketException: {0}", e.Message);
                    return false;
                }

                return true;
            }
            public void Stop()
            {
            }

            public delegate void OnAccepted_Handler(Object sender, Object newlink);

            public event OnAccepted_Handler OnAccepted;
            public LinkManager Links { get { return _links; } }

            //-------------------------------------------------------------------------------------------------------------------
            private void Internal_OnAccepted(IAsyncResult asyn)
            {
                Link link;
                int step = 0;
                try
                {
                    Socket s = _sock.EndAccept(asyn);
                    step = 10;
                    link = new Link(s);
                    step = 20;

                    link.OnClosed += Link_OnClosed;
                    step = 30;
                    lock (_links)
                    {
                        step = 40;
                        _links.Add(link);
                        step = 41;
                    }

                    if (null != OnAccepted)
                    {
                        step = 50;
                        OnAccepted(this, link);
                        step = 51;
                    }

                    if (_links.Count % 100 == 0)
                        log.InfoFormat("[{0}] _links.Add({0}): count={1}", link.InstanceID, _links.Count);
                    step = 60;
                    if (!link.BeginReceive())
                    {
                        step = 70;
                        log.ErrorFormat("[{0}] BeginReceive() failed: {0}", link.InstanceID);
                        step = 71;
                        link.Close();
                        step = 72;
                    }
                }
                catch (SocketException e)
                {
                    step += 100;
                    log.ErrorFormat("SocketException: {0} {1}", e.ErrorCode, e.Message);
                    step += 100;
                }
                finally
                {
                    step += 1000;
                    IAsyncResult result = _sock.BeginAccept(new AsyncCallback(Internal_OnAccepted), null);
                    step += 1000;
                }
                step += 10000;
                if (12060 != step)
                    log.FatalFormat("Internal_OnAccepted: step = {0}", step);
            }
            private void Link_OnClosed(Object sender)
            {
                Link link = (Link)sender;
                if (null == link) return;

                lock (_links)
                {
                    _links.Remove(link);
                    if (_links.Count % 100 == 0)
                        log.InfoFormat("[{0}] _links.Remove({0}): count={1}", link.InstanceID, _links.Count);
                }                
            }

            //-------------------------------------------------------------------------------------------------------------------
            protected static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
            private Socket _sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            protected LinkManager _links = new LinkManager();
        }

        public class Connector
        {
            // NOT_IMPL
        }

        public class AsyncIOContext
        {
        }
        public class AsyncSendContext : AsyncIOContext
        {
            private AsyncSendContext() { }
            public AsyncSendContext(Link link, byte[] buffer, int size, Object context)
            {
                Set(link, buffer, size, context);
            }
            public void Set(Link link, byte[] buffer, int size, Object context)
            {
                Debug.Assert(null != link);
                Debug.Assert(null != buffer);
                Debug.Assert(0 < size);
                Debug.Assert(buffer.Length >= size);

                _link = link;
                _buffer = buffer;
                _size = size;
                _context = context;
            }
            public void RemoveLeft(int size)
            {
                int remain_bytes = _size - size;

                Debug.Assert(0 < remain_bytes);
                Debug.Assert(_size == buffer.Length);

                if (0 >= remain_bytes)
                {
                    _buffer = null;
                    _size = 0;
                }
                else
                {
                    byte[] newbuffer = new byte[remain_bytes];
                    Buffer.BlockCopy(_buffer, size, newbuffer, 0, remain_bytes);
                    _buffer = newbuffer;
                    _size = remain_bytes;
                }
            }
            
            public Link link { get { return _link; } }
            public byte[] buffer { get { return _buffer; } }
            public int size { get { return _size; } }
            public Object context { get { return _context; } }

            private Link _link;
            private byte[] _buffer;
            private int _size;
            private Object _context;
        }
        public class AsyncReceiveContext : AsyncIOContext
        {
            public AsyncReceiveContext() { }
            public AsyncReceiveContext(Link link, int buffersize)
            {
                Set(link, buffersize);
            }
            public void Set(Link link, int buffersize)
            {
                _link = link;
                
                if (null != _buffer)
                    if (_buffer.Length >= buffersize)
                        return;
                
                _buffer = new byte[buffersize];
            }

            public const int default_buffersize = (8 * 1024);
            public Link link { get { return _link; } }
            public byte[] buffer { get { return _buffer; } }

            private Link _link;
            private byte[] _buffer;
        }
        public class AsyncCloseContext : AsyncIOContext
        {
            public AsyncCloseContext(Link link, int _reason)
            {
                Set(link, _reason);
            }
            public void Set(Link link, int _reason)
            {
                _link = link;
            }

            public Link link { get { return _link; } }
            public int reason { get { return _reason; } }

            private Link _link;
            private int _reason = 0;    // unused
        }
        public class AsyncConnectContext : AsyncIOContext
        {
        }
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        //  public class Link   /////////////////////////////////////////////////////////////////////////////////////////////////
        /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        public class Link//: IDisposable
        {
            //-------------------------------------------------------------------------------------------------------------------
            public delegate void OnSent_Handler(Object sender, byte[] buffer, int size, Object state);
            public delegate void OnReceived_Handler(Object sender, byte[] buffer, int size);
            public delegate void OnClosed_Handler(Object sender);

            public event OnSent_Handler OnSent;
            public event OnReceived_Handler OnReceived;
            public event OnClosed_Handler OnClosed;

            //-------------------------------------------------------------------------------------------------------------------
            public Link()
            {
                Init();
                WC.Pattern.Singleton<WC.Net.NetStatistics>.Instance.link_constructor();
            }
            public Link(Socket s) : this()
            {
                _sock = s;
            }
            ~Link()
            {
                Dispose(false);
                WC.Pattern.Singleton<WC.Net.NetStatistics>.Instance.link_destructor();
            }

            private bool Init()
            {
                RetrieveInstaceID();

                try
                {
                    _sock.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true);
                    _sock.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, true);
                    SocketExtensions.SetKeepAlive(_sock, 3000, 1);
                    //_sock.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.MaxConnections, );
                }
                catch (ObjectDisposedException de)
                {
                    log.ErrorFormat("Exception: ObjectDisposedException: {0}", de.Message);
                    Close();
                    return false;
                }
                catch (SocketException se)
                {
                    log.ErrorFormat("Exception: SocketException: {0}", se.Message);
                    Close();
                    return false;
                }
                
                if (_sock.Connected)
                    _state = SockState.Connected;
                else
                    _state = SockState.Initialized;

                OnReceived += WC.Pattern.Singleton<WC.Net.NetStatistics>.Instance.OnReceived;
                OnSent += WC.Pattern.Singleton<WC.Net.NetStatistics>.Instance.OnSent;
                OnClosed += WC.Pattern.Singleton<WC.Net.NetStatistics>.Instance.OnClosed;

                return true;
            }

            public bool Connect(IPEndPoint target)
            {
                if (_sock.Connected)    return false;

                try
                {
                    _sock.Connect(target);
                    _state = SockState.Connect_Pending;
                    WC.Pattern.Singleton<WC.Net.NetStatistics>.Instance.TryConnect();
                    WC.Pattern.Singleton<WC.Net.NetStatistics>.Instance.Connected();

                    if (!_sock.Connected || !BeginReceive())
                    {
                        Close();
                        return false;
                    }

                    _state = SockState.Connected;
                }
                catch (ObjectDisposedException de)
                {
                    log.ErrorFormat("Exception: ObjectDisposedException: {0}", de.Message);
                    Close();
                    return false;
                }
                catch (SocketException se)
                {
                    log.ErrorFormat("Exception: SocketException: {0}", se.Message);
                    Close();
                    return false;
                }

                return true;
            }
            public bool Connect(IPAddress addr, ushort port)
            {
                IPEndPoint ipEndPoint = new IPEndPoint(addr, port);
                return Connect(ipEndPoint);
            }
            public bool Connect(string addr, ushort port)
            {
                IPEndPoint ipEndPoint = NetUtils.Resolve(addr, port);
                return Connect(ipEndPoint);
            }

            public void Close()
            {
                lock (this)
                {
                    if (!IsConnected && (SockState.Close_Pending == _state))
                    {
                        return;
                    }

                    if (IsConnected)
                    {
                        try
                        {
                            _sock.Shutdown(SocketShutdown.Both);
                            _sock.Close();
                        }
                        catch (ObjectDisposedException de)
                        {
                            log.ErrorFormat("Exception: ObjectDisposedException: {0}", de.Message);
                        }
                        catch (SocketException se)
                        {
                            log.ErrorFormat("Exception: SocketException: {0}", se.Message);
                        }

                        lock(_sendQ)
                        {
                            log.WarnFormat("[{0}] _SendQ.Count = {1}", hSocket, _sendQ.Count);
                            _sendQ.Clear();
                        }
                    }

                    if (SockState.Close_Pending != _state)
                    {
                        _state = SockState.Close_Pending;
                        AsyncCloseContext ctx = new AsyncCloseContext(this, 0);
                        ThreadPool.QueueUserWorkItem(new WaitCallback(Internal_OnClosed), ctx);
                    }
                }
            }
            public bool IsConnected { get { return _sock.Connected; } }

            internal bool BeginReceive()
            {
                if (!_sock.Connected)
                    return false;
                if (null == _rcvd_ctx)
                    return false;

                _rcvd_ctx.Set(this, AsyncReceiveContext.default_buffersize);
                SocketError err;

                try
                {
                    IAsyncResult result = _sock.BeginReceive(_rcvd_ctx.buffer, 0, _rcvd_ctx.buffer.Length, SocketFlags.None, out err, new AsyncCallback(Internal_OnReceived), _rcvd_ctx);
                    if (SocketError.Success != err)
                    {
                        return false;
                    }
                }
                catch (ObjectDisposedException de)
                {
                    log.ErrorFormat("Exception: ObjectDisposedException: {0}", de.Message);
                    Close();
                    return false;
                }
                catch (SocketException se)
                {
                    log.ErrorFormat("Exception: SocketException: {0}", se.Message);
                    Close();
                    return false;
                }

                return true;
            }
            public bool Send(byte[] buffer, int size, Object state)
            {
                lock (_sendQ)
                {
                    try
                    {
                        bool doSend = (0 == _sendQ.Count);
                        AsyncSendContext ctx = new AsyncSendContext(this, buffer, size, state);

                        _sendQ.AddLast(ctx);

                        if (doSend)
                        {
                            if (!Internal_Send(ctx))
                            {
                                Close();
                                return false;
                            }
                        }
                    }
                    catch (Exception e)
                    {
                        log.ErrorFormat("Exception: {0}", e.Message);
                    }
                }

                return true;
            }
            private bool Internal_Send(AsyncSendContext ctx)
            {
                try
                {
                    SocketError err;

                    IAsyncResult result = _sock.BeginSend(ctx.buffer, 0, ctx.size, SocketFlags.None, out err, new AsyncCallback(Internal_OnSent), ctx);
                    if (SocketError.Success != err)
                        return false;

                    WC.Pattern.Singleton<WC.Net.NetStatistics>.Instance.SendPacket(ctx.size);
                }
                catch (ObjectDisposedException de)
                {
                    log.ErrorFormat("Exception: ObjectDisposedException: {0}", de.Message);
                    return false;
                }
                catch (SocketException se)
                {
                    log.ErrorFormat("Exception: SocketException: {0}", se.Message);
                    return false;
                }

                return true;
            }

            public IntPtr hSocket { get { return _sock.Handle; } }
            public IPEndPoint LocalEndPoint { get { return (IPEndPoint)_sock.LocalEndPoint; } }
            public IPEndPoint RemoteEndPoint { get { return (IPEndPoint)_sock.RemoteEndPoint; } }
            public MemoryStream Stream { get { return _stream; } }

            //-------------------------------------------------------------------------------------------------------------------
            // Internal Method & Variable
            private void Internal_OnSent(IAsyncResult asyn)
            {
                lock (this) //@ lock(_IOSyncRoot)
                {
                    AsyncSendContext ctx = (AsyncSendContext)asyn.AsyncState;
                    if (!_sock.Connected)
                    {
                        Close();
                        return;
                    }

                    try
                    {
                        SocketError err;
                        int len = _sock.EndSend(asyn, out err);
                        bool isSendCompleted = (len == ctx.size);

                        Debug.Assert(1 <= _sendQ.Count);
                        Debug.Assert(_sendQ.First.Value.Equals(ctx));

                        if (0 == len)
                        {
                            Close();
                            return;
                        }

                        if (!isSendCompleted)
                        {
                            log.WarnFormat("[{0}] ISNOT SendCompleted {1} + {2} = {3}", hSocket, len, ctx.size-len, ctx.size);
                            ctx.RemoveLeft(len);
                            if (!Internal_Send(ctx))
                            {
                                Close();
                                return;
                            }
                        }
                        else // isSendCompleted == true
                        {
                            Debug.Assert(len == ctx.size);

                            if (null != OnSent)
                                OnSent(this, ctx.buffer, len, ctx.context);

                            lock (_sendQ)
                            {
                                _sendQ.RemoveFirst();

                                if (0 < _sendQ.Count)
                                {
                                    AsyncSendContext next_ctx = _sendQ.First.Value;
                                    if (!Internal_Send(next_ctx))
                                    {
                                        Close();
                                        return;
                                    }
                                }
                            }
                        }
                    }
                    catch (ObjectDisposedException de)
                    {
                        log.ErrorFormat("Exception: ObjectDisposedException: {0}", de.Message);
                        Close();
                    }
                    catch (SocketException se)
                    {
                        log.ErrorFormat("Exception: SocketException: {0}", se.Message);
                        Close();
                    }
                }
            }
            private void Internal_OnReceived(IAsyncResult asyn)
            {
                lock (this)
                {
                    AsyncReceiveContext ctx = (AsyncReceiveContext)asyn.AsyncState;
                    if (!_sock.Connected)
                    {
                        //if (SockState.Close_Pending != _state)
                        Close();
                        return;
                    }

                    try
                    {
                        SocketError err;
                        int len = _sock.EndReceive(asyn, out err);
                        if (len == 0)
                        {
                            if (log.IsDebugEnabled) log.DebugFormat("[{0}] _sock.EndReceive(...) -> return 0", hSocket);
                            Close();
                        }
                        else
                        {
                            try
                            {
                                if (null != OnReceived)
                                    OnReceived(this, ctx.buffer, len);

                            }
                            catch
                            {
                                log.ErrorFormat("OnReceived: Exception raised");
                            }

                            if (!BeginReceive())
                            {
                                if (log.IsDebugEnabled) log.DebugFormat("[{0}] BeginReceive() failed", hSocket);
                                Close();
                            }
                        }
                    }
                    catch (ObjectDisposedException de)
                    {
                        log.ErrorFormat("ObjectDisposedException: {0}", de.Message);
                        Close();
                    }
                    catch (SocketException se)
                    {
                        log.ErrorFormat("SocketException: {0}", se.Message);
                        Close();
                    }
                }
            }
            private void Internal_OnClosed(object obj)
            {
                AsyncCloseContext ctx = (AsyncCloseContext)obj;
                lock (this)
                {
                    Debug.Assert(SockState.Close_Pending == _state);
                    //log.WarnFormat("[{0}] Internal_OnClosed(...) - state={1}", _sock.Handle, _state);
                    //InteranlAyncState ctx = (InteranlAyncState)asyn.AsyncState;
                    try
                    {
                        if (null != OnClosed)
                            OnClosed(this);

                        //_sock.EndDisconnect(asyn);
                    }
                    catch (Exception e)
                    {
                        log.ErrorFormat("Exception: {0}", e.Message);
                    }
                }
            }
            public void Dispose()
            {
                Dispose(true);
                WC.Pattern.Singleton<WC.Net.NetStatistics>.Instance.link_disposed();
            }
            protected virtual void Dispose(bool disposing)
            {
                //if (false == System.Threading.Interlocked.CompareExchange<bool>(ref value, true, false))
                if (false == _disposed)
                {
                    _disposed = true;
                    Close();

                    if (disposing)
                        GC.SuppressFinalize(this);
                }

                log.InfoFormat("[{0}] Dispose({1})", hSocket, disposing);
            }

            private Socket _sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            protected MemoryStream _stream = new MemoryStream();
            protected static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
            private bool _disposed = false;
            enum SockState
            {
                Unknown = 0,
                Initialized,
                Connect_Pending,
                Connected,
                Close_Pending,
                Closed
            };
            private SockState _state = SockState.Unknown;
            AsyncReceiveContext _rcvd_ctx = new AsyncReceiveContext();

            //@ LinkedList<IAsyncResult> 로...? LinkedList<MemoryStream> ??
            //private LinkedList<byte[]> _sendBuffers = new LinkedList<byte[]>();
            private LinkedList<AsyncSendContext> _sendQ = new LinkedList<AsyncSendContext>();

            private long RetrieveInstaceID()
            {
                _instance_id = System.Threading.Interlocked.Increment(ref _instance_seq);
                return _instance_id;
            }
            public long InstanceID { get { return _instance_id; } }
            private long _instance_id = 0;
            private static long _instance_seq = 0;
        }
    }

    class NetUtils
    {
        public static IPEndPoint Resolve(string ipOrHost, int port)
        {
            IPAddress adr = Resolve(ipOrHost);
            if (null == adr)
                return null;
            return new IPEndPoint(adr, port);
        }
        /// <summary>
        /// Get IPv4 address from notation (xxx.xxx.xxx.xxx) or hostname
        /// </summary>
        public static IPAddress Resolve(string ipOrHost)
        {
            Debug.Assert(!string.IsNullOrEmpty(ipOrHost));

            if (string.IsNullOrEmpty(ipOrHost))
            {
                //throw new ArgumentException("Supplied string must not be empty", "ipOrHost");
                return null;
            }

            ipOrHost = ipOrHost.Trim();

            IPAddress ipAddress = null;
            if (IPAddress.TryParse(ipOrHost, out ipAddress))
            {
                if (ipAddress.AddressFamily == AddressFamily.InterNetwork)
                    return ipAddress;
                //throw new ArgumentException("This method will not currently resolve other than ipv4 addresses");
                return null;
            }

            // ok must be a host name
            IPHostEntry entry;
            try
            {
                entry = Dns.GetHostEntry(ipOrHost);
                if (entry == null)
                    return null;

                // check each entry for a valid IP address
                foreach (IPAddress ipCurrent in entry.AddressList)
                {
                    if (ipCurrent.AddressFamily == AddressFamily.InterNetwork)
                        return ipCurrent;
                }

                return null;
            }
            catch (SocketException ex)
            {
                if (ex.SocketErrorCode == SocketError.HostNotFound)
                {
                    //LogWrite(string.Format(CultureInfo.InvariantCulture, "Failed to resolve host '{0}'.", ipOrHost));
                    return null;
                }
                else
                {
                    //throw;
                    return null;
                }
            }
        }
    }
}
